//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <ddk.h>
#include <mutex.h>
#include "_sprintf.h"
#include "neptune_ppp.h"
#include <nu_api.h>
#include <zenr_api.h>
#include "tapi_ppp.h"

#ifdef NU_CONNECT_TYPE_PPP

extern Thread *CreateSystemThread(
        threadroutine_t threadroutine, void *pvParameter,
        ScheduleClass *pScheduleClass, uint_t uSchedulePolicy);

typedef PVoid sio_fd_t;

extern "C" int pppSetAddr(int pd, UInt32 our,  UInt32 host,  UInt32 mask,  UInt32 dns1,
                UInt32 dns2);
extern "C" int pppGetAddr(int pd, UInt32 *our,  UInt32 *host,  UInt32 *mask,
                UInt32 *dns1, UInt32 *dns2);
extern "C" int pppGetDNS(UInt32 *dns1, UInt32 *dns2);// get dns from default interface
extern "C" int  pppClose(int pd);
extern "C" int  pppWaitUp(int pd);
extern "C" int  pppIOCtl(int pd, int cmd, void *arg);
extern "C" int  pppOpen(sio_fd_t fd,
                void (*linkStatusCB)(void *ctx, int errCode, void *arg),
                void *linkStatusCtx);
extern "C" void pppSetAuth(enum pppAuthType authType, const char *user,
                const char *passwd);

extern "C" void pppInit(void *arg);
extern "C" void pppStopCB(void *arg);

extern "C" void pppInProc(int pd, char *s, int l);
extern void lwipentry();


struct DATA_CNTR {
    int avail;
    void *data;
    unsigned long size;
};

static struct DATA_CNTR s_dataCntr[OUT_BUF_NUM];
static int s_nWrite;
static int s_nRead;
static DzEvent s_recvEvent(FALSE, UNSIGNALED);

static int InitRecvDataBuffer()
{
    s_nWrite = s_nRead = 0;
    for (int i = 0; i < OUT_BUF_NUM; i++) {
        s_dataCntr[i].avail = 0; // has no data
    }

    return 0;
}

static int receive_ppp(int devid, unsigned char *pdata, unsigned long nCount)
{
#ifdef NEVER
    char tmpBuf[100];
    sprintf(tmpBuf, "receive_ppp:--->size.%d in\n", nCount);
    nu_puts(tmpBuf);
    for (unsigned long i = 0; i < nCount; i++) {
        tmpBuf[0] = pdata[i];
        tmpBuf[1] = '-';
        tmpBuf[2] = '\0';
        nu_puts(tmpBuf);
    }
#endif /* NEVER */
    int bExistEmptyBuffer  = 0;

    if (!s_dataCntr[s_nWrite].avail) {
        s_dataCntr[s_nWrite].data = pdata;
        s_dataCntr[s_nWrite].size = nCount;
        s_dataCntr[s_nWrite].avail = 1;//set mark: that this buffer has a valid data
        bExistEmptyBuffer = 1;
        s_nWrite = (s_nWrite + 1) % OUT_BUF_NUM;
        nu_notifyDeviceCaller((Uint32)&s_recvEvent, 1);
    }
    else {
        nu_puts("receive_ppp:--->overflow, drop it\n");
    }
    return bExistEmptyBuffer;
}

ECode fnNeptuneReader(PVoid pvData)
{
    NeptunePPPTranceiver *pTran = (NeptunePPPTranceiver*)pvData;
    while(pTran->m_bThreadRun) {
        if (!s_dataCntr[s_nRead].avail) {
            WaitResult wr;
            EventState state;
            s_recvEvent.TryWait(DzMillisecondsToTicks(5000), &wr, &state);
            continue;
        }
        pppInProc(0, (char*)s_dataCntr[s_nRead].data, s_dataCntr[s_nRead].size);
        nu_getTapiPPP()->ppp_free(s_dataCntr[s_nRead].data);
        s_dataCntr[s_nRead].avail = 0;
        s_nRead = (s_nRead + 1) % OUT_BUF_NUM;
    }
    return NOERROR;
}

static int PppWrite(Int32 fd, struct pbuf *nb)
{
    if (fd < 0 || fd >= NUM_PPP) {
        kprintf("pppdrv.cpp PppWrite fd = %d\n", fd);
        return -1;
    }

    for (struct pbuf * b = nb; b != NULL; b = b->next) {
        kprintf("static int PppWrite ->%d in", b ? b->len : -1);
        nu_getTapiPPP()->ppp_send(0, (unsigned char*)b->payload, b->len);
        kprintf("static int PppWrite ->%d out\n", b->len);
    }

    return 0;
}

NeptunePPPTranceiver::NeptunePPPTranceiver()
{
    m_bThreadRun = FALSE;
    m_pThreadRead = NULL;
}

NeptunePPPTranceiver::~NeptunePPPTranceiver()
{
    m_bThreadRun = FALSE;

    if (m_pThreadRead) {
        WaitResult wait;
        m_pThreadRead->Join(INFINITE, &wait);
        m_pThreadRead->Release();
        m_pThreadRead = NULL;
    }
}

ECode NeptunePPPTranceiver::Init()
{
    m_bThreadRun = TRUE;
    m_pThreadRead = (IThread*)CreateSystemThread(fnNeptuneReader, this,
                    g_pDefaultScheduleClass, g_uDefaultSchedulePolicy);
    if (m_pThreadRead == NULL) {
        return E_OUT_OF_MEMORY;
    }

    return NOERROR;
}

ECode NeptunePPPTranceiver::Read(
    /* [in] */ Int64 u64Offset,
    /* [in] */ Int32 bytesToRead,
    /* [out] */ MemoryBuf * pBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode NeptunePPPTranceiver::Write(
    /* [in] */ Int64 u64Offset,
    /* [in] */ const MemoryBuf & buffer,
    /* [out] */ Int32 * pBytesWritten,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode NeptunePPPTranceiver::Control(
    /* [in] */ Handle32 nControlCode,
    /* [in] */ const MemoryBuf & inBuffer,
    /* [out] */ MemoryBuf * pOutBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    int pd = 0;
    switch (nControlCode) {
        case PPP_INIT:

            break;

        case PPP_SETAUTH:{
            PPPAuthData *authData = (PPPAuthData *)inBuffer.GetPayload();

            pppSetAuth(authData->authType, authData->user, authData->passwd);
        }
        break;

        case PPP_OPEN:{
            kprintf("-------pppdrv---------> PPP_OPEN 1--------\n");
            InitRecvDataBuffer();

            nu_getTapiPPP()->ppp_init(receive_ppp);

            pd = pppOpen((sio_fd_t)0, NULL, NULL);
            if (pd >= 0) {
                pOutBuffer->Copy((PByte)&pd, 4);
            }
            else {
                pOutBuffer->SetUsed(0);
            }
            kprintf("-------pppdrv---------> PPP_OPEN 2--------\n");
        }
        break;

        case PPP_WAITUP:{
            kprintf("-------pppdrv---------> PPP_WAITUP --------\n\n");

            pd = *(int *)inBuffer.GetPayload();

            if (pd >= 0 && pd < NUM_PPP) {
                int i = pppWaitUp(pd);
                if (i < 0) {
                    return E_ABORT;
                }
            }
        }
        break;

        case PPP_CLOSE:{
            pd = *(int *)inBuffer.GetPayload();
            if (pd >= 0 && pd < NUM_PPP) {
                pppClose(pd);
            }
        }
        break;

        case PPP_SETADDR:{
            pd = *(int *)inBuffer.GetPayload();

            PPPAddr pppAddr;
            if (pd >=0 && pd < NUM_PPP) {
                memcpy(&pppAddr, (char *)inBuffer.GetPayload() + 4,
                            sizeof(pppAddr));

                pppSetAddr(pd, pppAddr.our,  pppAddr.host,
                       pppAddr.mask,  pppAddr.dns1, pppAddr.dns2);
            }
        }
        break;

        case PPP_GETADDR:{
            pd = *(int *)inBuffer.GetPayload();
            PPPAddr pppAddr;

            if (pd >= 0 && pd < NUM_PPP) {
                pppGetAddr(pd, &pppAddr.our, &pppAddr.host,
                       &pppAddr.mask, &pppAddr.dns1, &pppAddr.dns2);

                pOutBuffer->Copy((PByte)&pppAddr, sizeof(pppAddr));
            }
        }
        break;

        case PPP_GETDNS: {
            UInt32 dns1 = 0, dns2 = 0;
            pppGetDNS(&dns1, &dns2);
            if (dns1) {
                pOutBuffer->Copy((PByte)&dns1, 4);
                if (dns2) {
                    pOutBuffer->Append((PByte)&dns2, 4);
                }
            }
        }
        break;

        case PPP_IOCTOL:{
            PPPIoctlData *ioctlData = (PPPIoctlData *)inBuffer.GetPayload();
            int ret = pppIOCtl(ioctlData->pd, ioctlData->cmd,
                            &ioctlData->arg);
            if (ret != 0) {
                pOutBuffer->SetUsed(0);
            }
            else {
                pOutBuffer->Copy((PByte)&ioctlData->pd, 4);
                pOutBuffer->Append((PByte)&ioctlData->cmd, 4);
                pOutBuffer->Append((PByte)&ioctlData->arg, 4);
                pOutBuffer->SetUsed(12);
            }
        }
        break;

        case PPP_READABORT:{
        }
        break;

        default:
            break;
    }

    return NOERROR;
}

void NeptunePPPTranceiver::Dispose()
{
}

struct PPP_INTERFACE {
    int (*fnPppWrite)(Int32 pd, struct pbuf *nb);
};

extern "C" struct PPP_INTERFACE g_pppInterface;

EXTERN IDeviceDriver * CDECL CreatePDrv(uint_t uDeviceNo, void *pvParameter)
{
    NeptunePPPTranceiver *pPppdrv = new NeptunePPPTranceiver();
    if (NULL == pPppdrv) {
        kprintf("out of memory\n");
        return NULL;
    }

    ECode ec = pPppdrv->Init();
    if (FAILED(ec)) {
        kprintf("PPP init fail.\n");
        delete pPppdrv;
        return NULL;
    }

    lwipentry();
    pPppdrv->AddRef();

    g_pppInterface.fnPppWrite = PppWrite;

    return pPppdrv;
}

#endif // NU_CONNECT_TYPE_PPP

